#!/usr/bin/env python

"""
An echo server that uses threads to handle multiple clients at a time.
Entering any line of input at the terminal will exit the server.
"""

import select
import socket
import sys
from threading import *
import getopt
import os

BUFSIZE = 1024

m_cli_list=Lock()
m_cli_dict=Lock()
m_thread_list=Lock()

global_cli_list=[]
global_cli_dict={}
global_thread_list=[]


def usage():
    print "USAGE:"
    print "IMPORTANT"
    print "!!!  Run application as sudo !!!"
    print "!!!  Avoid extra spaces. No spaces requires within the options See Example below !!!"

    print "Ex:"
    print "bash3.2# python server.py --port=4500"
    print "OR"
    print "bash3.2# python server.py --port=3503"
    
    print "\nALL options Required for execution\n"
    print "--port="
    print "\t<Port on which server would listen for connections>"
    print "\t Avoid Spaces. Integer value required"


    sys.exit()
    
def remove_entry(addr_tup,serv_sock):
    print "Addr Tup: ",addr_tup
    cliname = rev_look(addr_tup)
    print "CLINAME: ",cliname
    
    sendstr="***"+cliname+" has left the chat*** "
    send_to_all(sendstr,serv_sock,cliname)

    del global_cli_dict[cliname]
    global_cli_list.remove(addr_tup)

    print "Removed Entry for %s from dict" %(cliname)
    print global_cli_dict


def rev_look(addr_tup):
    for key,value in global_cli_dict.items():
        if (value == addr_tup) and (key != None):
            return key

def send_to_one(data,addr_tup,serv_sock):
    cliname = rev_look(addr_tup)
    split_data=[]
    sent_to=""
    data_to=""
    
    data=data.strip()
    
    split_data=data.split(':',1)

    sent_to = split_data[0]
    data_to = split_data[1]
    
    dest = sent_to.replace('\n','')
    dest = dest.replace('@','')
    dest = dest.replace('\\','')
    dest = dest.replace(' ','')
    data = data_to.replace('\n','')
    
    
    if (global_cli_dict.has_key(dest)):
        print "User Found"
        sendstr="\t\t(PRIVATE CHAT:"+cliname+") > "+data
        send_tup = global_cli_dict[dest]
        serv_sock.sendto(sendstr,send_tup)
    else:
        print "User not found"
        sendstr="[SERVER] "+dest+" is not found in the chat room " 
        serv_sock.sendto(sendstr,addr_tup)


def send_to_all(sendstr,serv_sock, myname):
    print sendstr
    sendstr=sendstr.strip()
    
    for cliname,addr_tup in global_cli_dict.items():
        if(cliname != myname):
            serv_sock.sendto(sendstr,addr_tup)


def client_register(cliname,addr_tup,serv_sock):
    if(global_cli_dict.has_key(cliname[1:])):
        print "cli found in dict :",cliname[1:]
        if(global_cli_dict[cliname[1:]] != addr_tup):
            print "Removing Duplicate Entry from Dict"
            old_addr_tup = global_cli_dict[cliname[1:]]
            global_cli_list.remove(old_addr_tup)

            global_cli_dict[cliname[1:]] = addr_tup 
            global_cli_list.append(addr_tup)
            
            return 'UPD'
        else:
            return 'NUPD'
    else:
        """Reg new users"""
        if cliname[0] == '#':
            print "Got Client Name for registration: ",cliname[1:]
            cliname = cliname.lstrip('#')
            
            global_cli_list.append(addr_tup)
            global_cli_dict[cliname]=addr_tup
            
            serv_sock.sendto('done',addr_tup)
            
            sendstr="***"+cliname+" has joined the chat*** "
            send_to_all(sendstr,serv_sock,cliname)
            return 'UPD'
        else:
            print "Got something else :",cliname,":"
            return 'ERR'

def cli_recv_data(data,addr_tup,serv_sock):
    cliname = rev_look(addr_tup)
    print "cliname: It exists here:",cliname
    data=data.strip()
    if data:
        if(data[0:5] == '\\exit'):
            remove_entry(addr_tup,serv_sock)
        elif((data[0]=='\\') and (data[1] == '@') and (':' in data)):
            send_to_one(data,addr_tup,serv_sock)
        else:
            sendstr="\t\t("+cliname+") > "+data
            send_to_all(sendstr,serv_sock,cliname)
    else:
        remove_entry(addr_tup,serv_sock)
 

class Server:
    def __init__(self,mylist):
        self.host = ''
        self.port = mylist[0]
        self.backlog = 5
        self.size = 1024
        self.server = None

    
    def open_socket(self):
        try:
            self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.server.bind((self.host,self.port))

            print "Server port binded" 
            print "Listening for connections on %s ports %d" %(self.host,self.port)

        except socket.error, (value,message):
            if self.server:
                self.server.close()
            print "Could not open socket: " + message
            sys.exit(1)

    
    def exit_all(self):
        print "Exit All"

        for (cliname,addr_tup) in global_cli_dict.items():
            print "Sending exit command to client: ",cliname
            self.server.sendto('\\exit',addr_tup)

        self.server.close()
        sys.exit()
        
    def run(self):
        self.open_socket()
        input = [self.server,sys.stdin]
        running = 1
        while running:
            inputready,outputready,exceptready = select.select(input,[],[])

            for s in inputready:

                if s == self.server:
                    # handle the server socket
                    print "[Server Thread] Accept Connection"
                    data,addr_tup = self.server.recvfrom(BUFSIZE)
                    print global_cli_dict
                    ret_val = client_register(data,addr_tup,self.server)
                    
                    if (ret_val == 'UPD'):
                        print "Cli Name received"
                    else:
                        print "Data Received"
                        cli_recv_data(data,addr_tup,self.server)
                        
                elif s == sys.stdin:
                    # handle standard input
                    data = sys.stdin.readline()
                    data=data.replace('\n','')
                    data=data.replace(' ','')
                    print data
                    if(data == '\\exit'):
                        print "NOW EXIT"
                        self.exit_all()
                        running = 0 
        # close all threads

        self.server.close()
        print "Server Exit"
        

def check_valid_port(input_port):
    """Function to check if the port entered by user is sane"""

    try:
        int_port = int(input_port)
    except:
        print "Error in the input port %s" %(input_port)
        print "Only numbers between 1-65535 allowed"
        return False
    
    if((int_port<0) or (int_port>65535)):
        return False
        print "Error in the input port %s" %(input_port)   
        print "Only numbers between 1-65535 allowed"

    else:
        return True

def mygetopt(myargv):
    myport = ''
    myip = ''
    
    if(len(myargv) == 2):
            opt,args = getopt.getopt(myargv[1:],"h:",['port='])	
            for option,value in opt:
                    if option == "-h" or option == "--help":
                            usage()
                    elif option in "--port":
                            myport=value
                            if(check_valid_port(myport)):
                                pass
                            else:
                                myport = ''
                                print "Error in port"
                                usage()
                    else:
                            print "Unhandled option"
                            usage()
    else:
            usage()

    return (int(myport),myip)



def clrscr():
	"""This function clears the screen according to the OS it is on."""
	if os.name == "posix":
		os.system('clear')
	else:
		os.system('CLS')

def main():
    clrscr()
    mylist=mygetopt(sys.argv)
    s = Server(mylist)
    s.run()
    
if __name__ == "__main__":
    main()
